package spring.cloud.tasks.common.cron_expression;

import lombok.Getter;
import lombok.Setter;

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

/**
 * 基于cronExpression算出下个有效的执行时间。
 */
@Setter
@Getter
public class CronExpression {
    //
    private final Date startTime;
    /**
     * cronExpression
     */
    private String cronExpressionString;
    private org.springframework.scheduling.support.CronExpression cronExpression = null;
    /**
     * 任务暂停时间段，日期段。
     */
    private String pauseDatePeriod = "";
    /**
     * 任务暂停时间段，小时分钟段。
     */
    private String pauseTimePeriod = "";


    public CronExpression(String cronExpressionString, String pauseDatePeriod, String pauseTimePeriod) {
        this.cronExpressionString = cronExpressionString;
        this.cronExpression = org.springframework.scheduling.support.CronExpression.parse(cronExpressionString);
        this.pauseDatePeriod = pauseDatePeriod;
        this.pauseTimePeriod = pauseTimePeriod;
        //
        this.startTime = new Date();
        computeFirstFireTime();//判断当前cronExpression是否会造成死循环问题
    }

    /**
     * 最底层的方法
     */
    private static boolean checkIsInDateOrTimePausePeriod(
            String periodsString, String splitChar,//
            int monthOrHour, int dayOrMinute) {//
        try {
            String[] periodStrings = periodsString.split(",");
            for (String periodString : periodStrings) {
                String[] strings = periodString.trim().split("-");
                if (strings != null && strings.length == 2) {
                    String left = strings[0].trim();
                    String right = strings[1].trim();
                    String[] lefts = left.split(splitChar);
                    String[] rights = right.split(splitChar);
                    if (lefts != null && lefts.length == 2 &&//
                            rights != null && rights.length == 2) {
                        int left0 = Integer.parseInt(lefts[0]);
                        int left1 = Integer.parseInt(lefts[1]);
                        int right0 = Integer.parseInt(rights[0]);
                        int right1 = Integer.parseInt(rights[1]);
                        boolean result = (monthOrHour > left0 || monthOrHour == left0 && dayOrMinute >= left1) &&//
                                (monthOrHour < right0 || monthOrHour == right0 && dayOrMinute <= right1);
                        if (result) {
                            return true;
                        }
                    } else {
                        throw new RuntimeException("模式错误");
                    }
                } else {
                    throw new RuntimeException("模式错误");
                }
            }
            return false;
        } catch (Exception e) {
            throw new RuntimeException("模式错误", e);
        }
    }

    public Date computeFirstFireTime() {
        Date nextFireTime = getFireTimeAfter0(new Date(getStartTime().getTime() - 1000l));
        if (nextFireTime == null) {
            throw new NullPointerException("nextFireTime");
        }
        //
        int count = 0;
        while (nextFireTime != null && isInPausePeriod(nextFireTime)) {
            count++;
            if (count > 1024 * 10000) {
                throw new IllegalStateException("获取下次执行时间失败:可能已经陷入死循环了");
            }
            nextFireTime = getFireTimeAfter0(nextFireTime);
        }
        return nextFireTime;
    }

    public Date getFireTimeAfter(Date afterTime) {
        if (afterTime == null) {
            throw new NullPointerException("afterTime");
        }
        Date nextFireTime = getFireTimeAfter0(afterTime);
        //
        while (nextFireTime != null && isInPausePeriod(nextFireTime)) {
            nextFireTime = getFireTimeAfter0(nextFireTime);
        }
        return nextFireTime;
    }

    /**
     * 最底层的方法
     */
    private Date getFireTimeAfter0(Date afterTime) {
        if (afterTime == null) {
            throw new NullPointerException("afterTime");
        }
        //fix
        //if (afterTime.before(startTime)) {
        // afterTime = new Date(startTime.getTime() - 1000l);
        //}
        Date date = DateUtils.asDate(cronExpression.next(DateUtils.asLocalDateTime(afterTime)));
        if (date == null) {
            throw new NullPointerException("底层计算出的下次运行时间为空");
        }
        return date;
    }

    /**
     * 最底层的方法
     */
    private boolean isInPausePeriod(Date date) {
        Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
        calendar.setTime(date);
        int month = calendar.get(Calendar.MONTH) + 1; // Calendar.MONTH begin from 0.
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        int minute = calendar.get(Calendar.MINUTE);
        //
        boolean dateIn = false;
        boolean pausePeriodDateIsEmpty = (pauseDatePeriod == null || pauseDatePeriod.trim().isEmpty());
        if (!pausePeriodDateIsEmpty) {
            dateIn = checkIsInDateOrTimePausePeriod(pauseDatePeriod, "/", month, day);
        }
        //
        boolean timeIn = false;
        boolean pausePeriodTimeIsEmpty = (pauseTimePeriod == null || pauseTimePeriod.trim().isEmpty());
        if (!pausePeriodTimeIsEmpty) {
            timeIn = checkIsInDateOrTimePausePeriod(pauseTimePeriod, ":", hour, minute);
        }
        //
        if (pausePeriodDateIsEmpty) {
            if (pausePeriodTimeIsEmpty) {
                return false;
            } else {
                return timeIn;
            }
        } else {
            if (pausePeriodTimeIsEmpty) {
                return dateIn;
            } else {
                return dateIn && timeIn;
            }
        }
    }
}

